// sktoolslib - common files for SK tools

// Copyright (C) 2012, 2020 - Stefan Kueng

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software Foundation,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//

#include "stdafx.h"
#include "HotKeyCtrl.h"
#include <CommCtrl.h>

#define PROP_OBJECT_PTR    MAKEINTATOM(ga.atom)
#define PROP_ORIGINAL_PROC MAKEINTATOM(ga.atom)

/*
 * typedefs
 */
class CGlobalAtom
{
public:
    CGlobalAtom(void)
    {
        atom = GlobalAddAtom(L"_HotKeyCtrl_Object_Pointer_"
                             L"\\{07B14ED2-C35E-438a-9B39-F4BAFE4E59EB}");
    }
    ~CGlobalAtom(void)
    {
        DeleteAtom(atom);
    }

    ATOM atom;
};

/*
 * Local variables
 */
static CGlobalAtom ga;

CHotKeyCtrl::CHotKeyCtrl(void)
    : m_hWnd(NULL)
    , m_pfnOrigCtlProc(NULL)
    , controldown(false)
    , shiftdown(false)
    , menudown(false)
    , lwindown(false)
{
}

CHotKeyCtrl::~CHotKeyCtrl(void)
{
}

BOOL CHotKeyCtrl::ConvertEditToHotKeyCtrl(HWND hwndCtl)
{
    // Subclass the existing control.
    m_pfnOrigCtlProc = (WNDPROC)GetWindowLongPtr(hwndCtl, GWLP_WNDPROC);
    SetProp(hwndCtl, PROP_OBJECT_PTR, (HANDLE)this);
    SetWindowLongPtr(hwndCtl, GWLP_WNDPROC, (LONG_PTR)(WNDPROC)_HotKeyProc);

    kb_hook = SetWindowsHookEx(WH_KEYBOARD, _KeyboardProc, NULL, GetCurrentThreadId());
    m_hWnd  = hwndCtl;

    return TRUE;
}

BOOL CHotKeyCtrl::ConvertEditToHotKeyCtrl(HWND hwndParent, UINT uiCtlId)
{
    return ConvertEditToHotKeyCtrl(GetDlgItem(hwndParent, uiCtlId));
}

void CHotKeyCtrl::SetHKText(WORD hk)
{
    wchar_t buf[MAX_PATH]    = {0};
    wchar_t result[MAX_PATH] = {0};
    BYTE    h                = hk >> 8;
    if (h & HOTKEYF_CONTROL)
    {
        LONG sc = MapVirtualKey(VK_CONTROL, MAPVK_VK_TO_VSC);
        sc <<= 16;
        GetKeyNameText(sc, buf, _countof(buf));
        wcscat_s(result, _countof(result), buf);
    }
    if (h & HOTKEYF_SHIFT)
    {
        if (result[0])
            wcscat_s(result, _countof(result), L" + ");
        LONG sc = MapVirtualKey(VK_SHIFT, MAPVK_VK_TO_VSC);
        sc <<= 16;
        GetKeyNameText(sc, buf, _countof(buf));
        wcscat_s(result, _countof(result), buf);
    }
    if (h & HOTKEYF_ALT)
    {
        if (result[0])
            wcscat_s(result, _countof(result), L" + ");
        LONG sc = MapVirtualKey(VK_MENU, MAPVK_VK_TO_VSC);
        sc <<= 16;
        GetKeyNameText(sc, buf, _countof(buf));
        wcscat_s(result, _countof(result), buf);
    }
    if (h & HOTKEYF_EXT)
    {
        if (result[0])
            wcscat_s(result, _countof(result), L" + ");
        LONG sc = MapVirtualKey(VK_LWIN, MAPVK_VK_TO_VSC);
        sc |= 0x0100;
        sc <<= 16;
        GetKeyNameText(sc, buf, _countof(buf));
        wcscat_s(result, _countof(result), buf);
    }
    if (result[0])
        wcscat_s(result, _countof(result), L" + ");

    LONG nScanCode = MapVirtualKey(LOBYTE(hk), MAPVK_VK_TO_VSC);
    switch (LOBYTE(hk))
    {
            // Keys which are "extended" (except for Return which is Numeric Enter as extended)
        case VK_INSERT:
        case VK_DELETE:
        case VK_HOME:
        case VK_END:
        case VK_NEXT:  // Page down
        case VK_PRIOR: // Page up
        case VK_LEFT:
        case VK_RIGHT:
        case VK_UP:
        case VK_DOWN:
        case VK_SNAPSHOT:
            nScanCode |= 0x0100; // Add extended bit
    }
    nScanCode <<= 16;
    GetKeyNameText(nScanCode, buf, _countof(buf));

    wcscat_s(result, _countof(result), buf);
    ::SetWindowText(m_hWnd, result);
}

LRESULT CALLBACK CHotKeyCtrl::_KeyboardProc(int code, WPARAM wParam, LPARAM lParam)
{
    HWND hWndFocus = ::GetFocus();
    if (hWndFocus)
    {
        CHotKeyCtrl *pHyperLink = (CHotKeyCtrl *)GetProp(hWndFocus, PROP_OBJECT_PTR);
        if ((pHyperLink) && (pHyperLink->m_hWnd == hWndFocus))
        {
            if (lParam & 0x80000000)
            {
                // WM_KEYUP
                if (wParam == VK_CONTROL)
                {
                    pHyperLink->controldown = false;
                }
                else if (wParam == VK_SHIFT)
                {
                    pHyperLink->shiftdown = false;
                }
                else if (wParam == VK_MENU)
                {
                    pHyperLink->menudown = false;
                }
                else if (wParam == VK_LWIN)
                {
                    pHyperLink->lwindown = false;
                }
                else
                {
                    WORD hk = 0;
                    if (pHyperLink->controldown)
                    {
                        hk |= HOTKEYF_CONTROL;
                    }
                    if (pHyperLink->shiftdown)
                    {
                        hk |= HOTKEYF_SHIFT;
                    }
                    if (pHyperLink->menudown)
                    {
                        hk |= HOTKEYF_ALT;
                    }
                    if (pHyperLink->lwindown)
                    {
                        hk |= HOTKEYF_EXT;
                    }

                    pHyperLink->hotkey = MAKEWORD(wParam, hk);
                    pHyperLink->SetHKText(pHyperLink->hotkey);
                    return 1; //we processed it
                }
            }
            else
            {
                // WM_KEYDOWN
                if (wParam == VK_CONTROL)
                {
                    pHyperLink->controldown = true;
                }
                else if (wParam == VK_SHIFT)
                {
                    pHyperLink->shiftdown = true;
                }
                else if (wParam == VK_MENU)
                {
                    pHyperLink->menudown = true;
                }
                else if (wParam == VK_LWIN)
                {
                    pHyperLink->lwindown = true;
                }
                else
                {
                    WORD hk = 0;
                    if (pHyperLink->controldown)
                    {
                        hk |= HOTKEYF_CONTROL;
                    }
                    if (pHyperLink->shiftdown)
                    {
                        hk |= HOTKEYF_SHIFT;
                    }
                    if (pHyperLink->menudown)
                    {
                        hk |= HOTKEYF_ALT;
                    }
                    if (pHyperLink->lwindown)
                    {
                        hk |= HOTKEYF_EXT;
                    }
                    pHyperLink->hotkey = MAKEWORD(wParam, hk);
                    pHyperLink->SetHKText(pHyperLink->hotkey);
                    return 1; //we processed it
                }
            }
            return CallNextHookEx(pHyperLink->kb_hook, code, wParam, lParam);
        }
    }
    return 0;
}

LRESULT CALLBACK CHotKeyCtrl::_HotKeyProc(HWND hwnd, UINT message,
                                          WPARAM wParam, LPARAM lParam)
{
    CHotKeyCtrl *pHyperLink = (CHotKeyCtrl *)GetProp(hwnd, PROP_OBJECT_PTR);

    switch (message)
    {
        case WM_SHOWWINDOW:
            pHyperLink->SetHKText(pHyperLink->hotkey);
            break;
        case WM_GETDLGCODE:
            return DLGC_WANTALLKEYS;
        case WM_SYSKEYDOWN:
        case WM_KEYDOWN:
        {
            return 0;
        }
        case WM_SYSKEYUP:
        case WM_KEYUP:
        {
            return 0;
        }
        case WM_CHAR:
            return 0;
        case WM_DESTROY:
        {
            SetWindowLongPtr(hwnd, GWLP_WNDPROC,
                             (LONG_PTR)pHyperLink->m_pfnOrigCtlProc);

            RemoveProp(hwnd, PROP_OBJECT_PTR);
            break;
        }
    }

    return CallWindowProc(pHyperLink->m_pfnOrigCtlProc, hwnd, message,
                          wParam, lParam);
}

void CHotKeyCtrl::SetHotKey(WPARAM hk)
{
    hotkey = (WORD)hk;
    SetHKText((WORD)hk);
}

WPARAM CHotKeyCtrl::GetHotKey()
{
    return hotkey;
}
